﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Criterion;
using TestBat.IModels.Domain;
using TestBat.IModels.Models;
using TestBat.IModels.Repositories;
using TestBat.Models.Attributes;
using TestBat.Models.Exceptions;
using TestBat.ServiceLocator;

namespace TestBat.Models.Models
{
    public class TestModel : Model, ITestModel
    {
        public TestModel(ITestRepository testRepository)
        {
            this.testRepository = testRepository;
        }

        [Conversation]
        public ITest GetByID(Guid ID)
        {
            return testRepository.GetByID(ID);
        }

        [Conversation(ConversationEndMode.CommitAndContinue)]
        public void Save(ITest test)
        {
            Validate(test);

            testRepository.MakePersistent(test);
        }

        [Conversation(ConversationEndMode.CommitAndContinue)]
        public void Delete(ITest test)
        {
            testRepository.MakeTransient(test);
        }

        [Conversation(ConversationEndMode.CommitAndContinue)]
        public void SaveEssaySolution(Guid testTaskID, string solution)
        {
            ITestTask testTask = testRepository.GetTestTaskByID(testTaskID);
            if (testTask == null)
                throw new BusinessException("Saving essay solution failed", new ArgumentException("Invalid TestTask ID"));

            // check, if test is already finished
            if (_IsFinished(testTask.Test))
                throw new BusinessException("Saving essay solution failed", new InvalidOperationException("Test already finished"));

            //if (testTask.Solution == null)
            //    testTask.Solution = DefaultServiceLocator.Get<IEssaySolution>();
            (testTask.Solution as IEssaySolution).Text = solution;

            testRepository.MakePersistent(testTask.Test);
        }

        [Conversation(ConversationEndMode.CommitAndContinue)]
        public void SaveMultipleChoiceSolution(Guid testTaskID, Guid choiceID)
        {
            ITestTask testTask = testRepository.GetTestTaskByID(testTaskID);
            if (testTask == null)
                throw new BusinessException("Saving multiple choice solution failed", new ArgumentException("Invalid TestTask ID"));

            // check, if test is already finished
            if (_IsFinished(testTask.Test))
                throw new BusinessException("Saving multiple choice solution failed", new InvalidOperationException("Test already finished"));

            ITestChoice choice = (from c in (testTask as ITestMultipleChoiceTask).Choices where c.ID == choiceID select c).FirstOrDefault<ITestChoice>();

            if (choice == null)
                throw new BusinessException("Saving multiple choice solution failed", new ArgumentException("Invalid choiceID"));

            (testTask.Solution as IMultipleChoiceSolution).Pick = choice;

            testRepository.MakePersistent(testTask.Test);
        }

        [Conversation(ConversationEndMode.CommitAndContinue)]
        public void SaveMultipleResponseSolution(Guid testTaskID, Guid[] choiceIDs)
        {
            ITestTask testTask = testRepository.GetTestTaskByID(testTaskID);
            if (testTask == null)
                throw new BusinessException("Saving multiple response solution failed", new ArgumentException("Invalid TestTask ID"));

            // check, if test is already finished
            if (_IsFinished(testTask.Test))
                throw new BusinessException("Saving multiple response solution failed", new InvalidOperationException("Test already finished"));

            IList<ITestChoice> choices = new List<ITestChoice>();
            if (choiceIDs != null && choiceIDs.Length != 0)
                choices = (from c in (testTask as ITestMultipleResponseTask).Choices where (from id in choiceIDs where c.ID == id select id).Count<Guid>() != 0 select c).ToList<ITestChoice>();

            (testTask.Solution as IMultipleResponseSolution).Picks.Clear();
            foreach (var choice in choices)
                (testTask.Solution as IMultipleResponseSolution).Picks.Add(choice);

            testRepository.MakePersistent(testTask.Test);
        }

        [Conversation(ConversationEndMode.CommitAndContinue)]
        public ITest Create(ISchedule schedule, ITrainee trainee)
        {
            if (schedule == null || trainee == null)
                throw new BusinessException("Creating test failed", new ArgumentException("No schedule and/or trainee specified"));

            // check, if the trainee is scheduled for the test

            if ((from tg in schedule.TraineeGroups where (from tg2 in trainee.TraineeGroups where tg2 == tg select tg2).Count<ITraineeGroup>() != 0 select tg.TraineeGroup).Count<ITraineeGroup>() != 0)
                throw new BusinessException("Creating test failed", new ArgumentException("Trainee not scheduled for test"));

            // check, if the test already exists
            DetachedCriteria c = DetachedCriteria.For<ITest>().Add(Expression.Eq("Trainee", trainee)).Add(Expression.Eq("Schedule", schedule));
            ITest t = testRepository.GetByCriteria<ITest>(c).FirstOrDefault<ITest>();
            if (t != null)
                return t;

            // check, if test is already finished
            if (t != null && _IsFinished(t))
                throw new BusinessException("Creating test failed", new InvalidOperationException("Test is already finished"));

            ITest test = DefaultServiceLocator.Get<ITest>(); // TODO: use DI
            test.Schedule = schedule;
            test.Trainee = trainee;
            test.StartTime = DateTime.Now;

            // create test-tasks depending on schedule
            foreach (IScheduleTask st in schedule.Tasks)
            {
                // create appropriate testtask
                ITestTask tt = null;
                if (st.Task is IEssayTask)
                {
                    ITestEssayTask ttt = DefaultServiceLocator.Get<ITestEssayTask>();
                    ttt.Question = (st.Task as IEssayTask).Question;
                    IEssaySolution s = DefaultServiceLocator.Get<IEssaySolution>();
                    s.Text = "";
                    ttt.Solution = s;
                    tt = ttt;
                }
                else if (st.Task is IMultipleChoiceTask)
                {
                    ITestMultipleChoiceTask ttt = DefaultServiceLocator.Get<ITestMultipleChoiceTask>();
                    ttt.Question = (st.Task as IMultipleChoiceTask).Question;
                    foreach (var choice in (st.Task as IMultipleChoiceTask).Choices)
                    {
                        ITestChoice tc = DefaultServiceLocator.Get<ITestChoice>();
                        tc.Correct = choice.Correct;
                        tc.Task = ttt;
                        tc.Text = choice.Text;
                        ttt.Choices.Add(tc);
                    }
                    ttt.Solution = DefaultServiceLocator.Get<IMultipleChoiceSolution>();
                    tt = ttt;
                }
                else if (st.Task is IMultipleResponseTask)
                {
                    ITestMultipleResponseTask ttt = DefaultServiceLocator.Get<ITestMultipleResponseTask>();
                    ttt.Question = (st.Task as IMultipleResponseTask).Question;
                    foreach (var choice in (st.Task as IMultipleResponseTask).Choices)
                    {
                        ITestChoice tc = DefaultServiceLocator.Get<ITestChoice>();
                        tc.Correct = choice.Correct;
                        tc.Task = ttt;
                        tc.Text = choice.Text;
                        ttt.Choices.Add(tc);
                    }
                    ttt.Grading = (st.Task as IMultipleResponseTask).Grading;
                    ttt.Solution = DefaultServiceLocator.Get<IMultipleResponseSolution>();
                    tt = ttt;
                }
                else
                    throw new BusinessException("Schedule contains unrecognized task", new ArgumentException("Invalid schedule"));

                tt.Sort = st.Sort;
                tt.Test = test;
                tt.MaximumPoints = st.Task.MaximumPoints;
            }

            testRepository.MakePersistent(test);

            return test;
        }

        [Conversation(ConversationEndMode.CommitAndContinue)]
        public void Finish(ITest test)
        {
            if (test == null)
                throw new BusinessException("Finishing test failed", new ArgumentException("Invalid test"));

            if (_IsFinished(test))
                throw new BusinessException("Finishing test failed", new InvalidOperationException("Test is already finished"));

            test.FinishTime = DateTime.Now;

            testRepository.MakePersistent(test);
        }

        [Conversation]
        public bool IsFinished(ITest test)
        {
            return _IsFinished(test);
        }

        [Conversation]
        public IList<ITest> GetGradedForTrainee(ITrainee trainee)
        {
            var all = testRepository.GetAll();
            return (from t in all where t.IsClosed && t.IsGraded && t.Trainee.Equals(trainee) select t).ToList<ITest>();
        }

        private bool _IsFinished(ITest test)
        {
            if (test.FinishTime.HasValue)
                return true;

            if (test.StartTime + test.Schedule.Time < DateTime.Now)
                return true;

            return false;
        }

        private readonly ITestRepository testRepository;
    }
}
